#import "NiftyMatrix.h"
#import "MultiView.h"
#import <math.h>

@implementation MultiView

- initFrame:(const NXRect *)frm
{
	[super initFrame:frm];
	sel_list = [matrix getSelectedCells:nil];
	num = [sel_list count];
	if (num > MAX_VIEWS) num = MAX_VIEWS;
	currentLay = 0;
	[self initLayouts];
	return self;
}

//
// deal with defaults
//

- readDefaults
{
	int i = 0, j;
	char *def;
	const char *tmp;
	char *current;
	
	tmp = NXGetDefaultValue([NXApp appName],"MultiViews");
	if (tmp) {
		def = malloc(strlen(tmp) + 1);
		strcpy (def,tmp);
		while (def) {
			current = rindex(def,':');
			if (!current) {
				views[i] = [self viewNamed:def];
				j = [self cellNamed:def];
				[matrix setSelectionFrom:j to:j anchor:j lit:YES];
				break;
			}
			views[i] = [self viewNamed:(current+1)];
			i++;
			j = [self cellNamed:(current+1)];
			[matrix setSelectionFrom:j to:j anchor:j lit:YES];
			*current = '\0';
		}
		free(def);
	}
	tmp = NXGetDefaultValue([NXApp appName],"MultiLayout");
	if (tmp)
		currentLay = atoi(tmp);
	printf("%s : %d\n",tmp,currentLay);
	return self;
}

- writeDefaults:sender
{
	int i;
	const char *name;
	char def[1024]; // more than we should ever need
	
	if (num > 0) {
		def[0] = '\0';
		for (i = 0; i < num; i++) {
			if (i)
				strcat(def,":");
			name = [[sel_list objectAt:i] stringValue];
			strcat(def,name);
		}
		NXWriteDefault([NXApp appName], "MultiViews", def);
	}
	sprintf(def,"%d",currentLay);
	NXWriteDefault([NXApp appName], "MultiLayout",def);
	return self;
}


//
// whenever we receive a newWindow message, it's a good time to
// check the list and reinitialize everything
// this method is called internally everytime a new selection is made
- newWindow
{
	int i, n, lays;
	int x,y,w,h;
	NXRect frm;

	// clean up all of the old views.  reset their origins
	NXSetRect(&frm,0,0,50,50);
	for (i = 0; i < num; i++)
			[[views[i] setFrame:&frm] removeFromSuperview];
	
	// clean up the screen... what a mess!
	[self display];
//	[self lockFocus];
//	[self drawSelf:&frame :1];
//	[self unlockFocus];
	
	// find out what was selected, and get the appropriate views
	sel_list = [matrix getSelectedCells:nil];
	num = [sel_list count];
	lays = lay_defs[currentLay].num;
	n = MIN(lays,num);
	num = n;
	for (i = 0; i < n; i++) {
		views[i] = [self viewFrom:[sel_list objectAt:i]];
		if (NX_X(&lay_defs[currentLay].pos[i]) == 0)
			x = 0;
		else
			x = NX_WIDTH(&frame) / NX_X(&lay_defs[currentLay].pos[i]);
		if (NX_Y(&lay_defs[currentLay].pos[i]) == 0)
			y = 0;
		else
			y = NX_HEIGHT(&frame) / NX_Y(&lay_defs[currentLay].pos[i]);
		w = NX_WIDTH(&frame) / NX_WIDTH(&lay_defs[currentLay].pos[i]);
		h = NX_HEIGHT(&frame) / NX_HEIGHT(&lay_defs[currentLay].pos[i]);
		NXSetRect(&frm,x,y,w,h);
		[[views[i] setFrame:&frm] setClipping:YES];
		[self addSubview:views[i]];
		// we need to make sure that they draw themselves once first
		// [views[i] lockFocus];
		[views[i] display];
		// [views[i] unlockFocus];
	}
	return self;
}


//
// rather than call oneStep on every method at once,
// just call it on one, then next time, call it on the next one.
// for some reason, this seemed to improve performance
// (I'm not sure why though)
//
- oneStep
{
	static int cur = 0;
	[views[cur] lockFocus];
	// this is necessary for some views (such as Space) to function properly
	if ([views[cur] respondsTo:@selector(didLockFocus)])
		[views[cur] didLockFocus];
	[views[cur] oneStep];
	[views[cur] unlockFocus];
	cur++;
	if (cur >= num) 
		cur = 0;
	return self;
}


//
// when this is called, we know that we've just been loaded up
// so we take advantage of that fact and do a lot of initialization
//
- inspector:sender
{
  char buf[MAXPATHLEN];
  NXRect frm, scrollRect;
  NXSize matSize, cellSize;
  
  thinker = sender;
  pub = (struct thinkerDef *)thinker;
  modList = pub->moduleList;
  if (!inspectorPanel)
    {
	  // 
	  // Time to make the matrix... time to make the matrix
	  //
      sprintf(buf,"%s/Multi.nib",(char *)[sender moduleDirectory:"Multi"]);
      [NXApp loadNibFile:buf owner:self withNames:NO];
	  [scrollView getFrame:&frm];
	  matrix = [[NiftyMatrix alloc] initFrame:&frm
	  								mode:NX_LISTMODE
									cellClass:[Cell class]
									numRows:0
									numCols:1];
	  [self fillMatrix];
	  [scrollView getFrame:&scrollRect];
	  [ScrollView getContentSize:&matSize
	  			 	forFrameSize:&scrollRect.size
					horizScroller:NO
					vertScroller:YES
					borderType:NX_BEZEL];
	  [matrix getCellSize:&cellSize];
	  cellSize.width = matSize.width;
	  [matrix setCellSize:&cellSize];
	  [matrix sizeToCells];
	  [matrix setAutosizeCells:YES];
	  [matrix setAutoscroll:YES];
	  [scrollView setDocView:matrix];
	  [[matrix superview] setAutoresizeSubviews:YES];
	  [matrix setAutosizing:NX_WIDTHSIZABLE];
	  [matrix setTarget:self];
	  [matrix setAction:@selector(select:)];
	  [matrix setDoubleAction:@selector(inspect:)];
	  [self readDefaults];
	  //
	  // time to make the funky layout thingie
	  //
	// yuck... there must be a better way to initialize arrays like this
	layouts[0] = lay1;
	layouts[1] = lay2;
	layouts[2] = lay3;
	layouts[3] = lay4;
	layouts[4] = lay5;
	layouts[5] = lay6;
	layouts[6] = lay7;
	layouts[7] = lay8;
	layouts[8] = lay9;
	layouts[9] = lay10;
	layouts[10] = lay11;

	[layouts[currentLay] getFrame:&frm];
	NXSetRect(&frm,0,0,NX_WIDTH(&frm),NX_HEIGHT(&frm));
	[layouts[currentLay] setFrame:&frm];
	[[layout addSubview:layouts[currentLay]] display];
	}
  return inspectorPanel;
}


//
// clean up the "multi inspector"
//
- inspectorWillBeRemoved
{
	int i;
	NXRect foo;
	NXSetRect(&foo,0,0,0,0);
	for (i = 0; i < num; i++)
			[views[i] setFrame:&foo];
	if (otherInspector)
		[otherInspector close];
	return self;
}

- setImage:image
{
	int i;
	for (i = 0; i < num; i++)
		if ([views[i] respondsTo:@selector(setImage:)])
			[views[i] setImage:image];
	return self;
}

//
// helper function to -fillMatrix
//
- addCellWithString:(const char *)str at:(int)row
{
	id theCell;
	
	[matrix insertRowAt:row];
	theCell = [matrix cellAt:row :0];
	[theCell setStringValue:str];
	return self;
}

//
// fill the matrix with the view names
///
- fillMatrix
{
  int i, n, j;

  n = [modList count];
  for (i = j = 0; i < n; i++)
	{
		if (strcmp([modList nameAt: i], "Multi"))
#ifdef MULTI_LOCALIZED
			[self addCellWithString:NXLocalString([modList nameAt: i], 0, 0)
#else
			[self addCellWithString:[modList nameAt:i]
#endif							
						at:(j++)];
	}
  return self;
}

//
// called whenever an item in the matrix is clicked on
//
- select:sender
{
	[self newWindow];
	[[self window] flushWindow];
	return self;
}


//
// funky layout selection interface code
//

- layoutUp:sender
{
	NXRect frm;
	[layouts[currentLay] removeFromSuperview];
	currentLay --;
	if (currentLay < 0)
		currentLay = 10;
	[layouts[currentLay] getFrame:&frm];
	NXSetRect(&frm,0,0,NX_WIDTH(&frm),NX_HEIGHT(&frm));
	[layouts[currentLay] setFrame:&frm];
	[[layout addSubview:layouts[currentLay]] display];
	[self newWindow];
	[[self window] flushWindow];
	return self;
}

- layoutDown:sender
{
	NXRect frm;
	[layouts[currentLay] removeFromSuperview];
	currentLay ++;
	if (currentLay > 10)
		currentLay = 0;
	[layouts[currentLay] getFrame:&frm];
	NXSetRect(&frm,0,0,NX_WIDTH(&frm),NX_HEIGHT(&frm));
	[layouts[currentLay] setFrame:&frm];
	[[layout addSubview:layouts[currentLay]] display];
	[self newWindow];
	[[self window] flushWindow];
	return self;
}

//
// initialize all of the layout definitions.
// this is ugly because it's all hardcoded.  If I ever mess with the
// layouts in the interface, I'll also have to change this.  Unfortunately,
// I didn't see a straightforward way of doing it automatically from the drawn
// layouts.  Maybe I should do it the other way, and draw the layouts by hand.
// that probably makes sense if I ever do any future development on this
//
- initLayouts
{
	lay_defs[0].num = 1;
	[self allocLayout:0];
	NXSetRect(&lay_defs[0].pos[0],0,0,1,1);
	lay_defs[1].num = 2;
	[self allocLayout:1];
	NXSetRect(&lay_defs[1].pos[0],0,2,1,2);
	NXSetRect(&lay_defs[1].pos[1],0,0,1,2);
	lay_defs[2].num = 2;
	[self allocLayout:2];
	NXSetRect(&lay_defs[2].pos[0],0,0,2,1);
	NXSetRect(&lay_defs[2].pos[1],2,0,2,1);
	lay_defs[3].num = 3;
	[self allocLayout:3];
	NXSetRect(&lay_defs[3].pos[0],0,0,2,1);
	NXSetRect(&lay_defs[3].pos[1],2,2,2,2);
	NXSetRect(&lay_defs[3].pos[2],2,0,2,2);
	lay_defs[4].num = 3;
	[self allocLayout:4];
	NXSetRect(&lay_defs[4].pos[0],0,0,2,2);
	NXSetRect(&lay_defs[4].pos[1],0,2,2,2);
	NXSetRect(&lay_defs[4].pos[2],2,0,2,1);
	lay_defs[5].num = 3;
	[self allocLayout:5];
	NXSetRect(&lay_defs[5].pos[0],0,2,1,2);
	NXSetRect(&lay_defs[5].pos[1],0,0,2,2);
	NXSetRect(&lay_defs[5].pos[2],2,0,2,2);
	lay_defs[6].num = 3;
	[self allocLayout:6];
	NXSetRect(&lay_defs[6].pos[0],0,0,1,2);
	NXSetRect(&lay_defs[6].pos[1],0,2,2,2);
	NXSetRect(&lay_defs[6].pos[2],2,2,2,2);
	lay_defs[7].num = 4;
	[self allocLayout:7];
	NXSetRect(&lay_defs[7].pos[0],0,0,2,2);
	NXSetRect(&lay_defs[7].pos[1],2,2,2,2);
	NXSetRect(&lay_defs[7].pos[2],0,2,2,2);
	NXSetRect(&lay_defs[7].pos[3],2,0,2,2);
	lay_defs[8].num = 6;
	[self allocLayout:8];
	NXSetRect(&lay_defs[8].pos[0],0,0,2,3);
	NXSetRect(&lay_defs[8].pos[1],2,0,2,3);
	NXSetRect(&lay_defs[8].pos[2],0,3,2,3);
	NXSetRect(&lay_defs[8].pos[3],2,3,2,3);
	NXSetRect(&lay_defs[8].pos[4],0,1.5,2,3);
	NXSetRect(&lay_defs[8].pos[5],2,1.5,2,3);
	lay_defs[9].num = 6;
	[self allocLayout:9];
	NXSetRect(&lay_defs[9].pos[0],0,0,3,2);
	NXSetRect(&lay_defs[9].pos[1],3,0,3,2);
	NXSetRect(&lay_defs[9].pos[2],1.5,0,3,2);
	NXSetRect(&lay_defs[9].pos[3],0,2,3,2);
	NXSetRect(&lay_defs[9].pos[4],3,2,3,2);
	NXSetRect(&lay_defs[9].pos[5],1.5,2,3,2);
	lay_defs[10].num = 9;
	[self allocLayout:10];
	NXSetRect(&lay_defs[10].pos[0],0,0,3,3);
	NXSetRect(&lay_defs[10].pos[1],3,3,3,3);
	NXSetRect(&lay_defs[10].pos[2],1.5,1.5,3,3);
	NXSetRect(&lay_defs[10].pos[3],0,3,3,3);
	NXSetRect(&lay_defs[10].pos[4],3,0,3,3);
	NXSetRect(&lay_defs[10].pos[5],0,1.5,3,3);
	NXSetRect(&lay_defs[10].pos[6],1.5,0,3,3);
	NXSetRect(&lay_defs[10].pos[7],3,1.5,3,3);
	NXSetRect(&lay_defs[10].pos[8],1.5,3,3,3);
	return self;
}

- allocLayout:(int)index
{
	int n;
	n = lay_defs[index].num;
	lay_defs[index].pos = malloc(sizeof(NXRect) * n);
	return self;
}

//
// BackSpace Thinker emulation code
//


- performMethodOnViews:(SEL)sel
{
	int i;
	for (i = 0; i < num; i++)
		if ([views[i] respondsTo:sel])
			[views[i] perform:sel];
	return self;
}

- enteredScreenSaverMode
{
	[self performMethodOnViews:@selector(enteredScreenSaverMode)];
	return self;
}

- willExitScreenSaverMode
{
	[self performMethodOnViews:@selector(willExitScreenSaverMode)];
	return self;
}

- inspect:sender
{
	id insp = nil;
	NXRect frm;
	
	if ([[self viewFrom:sender] respondsTo:@selector(inspector:)])
		insp = [[self viewFrom:sender] inspector:thinker];
	if (insp == nil)
		insp = [thinker nullInspector];
	[insp getFrame:&frm];
	[otherInspector setContentView:insp];
	[otherInspector makeKeyAndOrderFront:self];
	[otherInspector sizeWindow:NX_WIDTH(&frm) :NX_HEIGHT(&frm)];
	[[otherInspector contentView] display];
	return self;
}


//
// these methods walk the list of views to match names and cells with
// the views that go with them
//

- viewFrom:sender
{
	const char *name;
	int i, n;
	id view = nil;
	
	name = [sender stringValue];
	n = [modList count];
	for (i = 0; i < n; i++)
		if (!strcmp([modList nameAt: i], name))
			view = [modList viewAt: i];
	if (view == nil)
		view = [self loadView:name];
	if (view == nil)
		fprintf(stderr,"returned nil: %s\n",name);
	return view;
}

- viewNamed:(const char *)name
{
	int i, n;
	id view = nil;
	
	n = [modList count];
	for (i = 0; i < n; i++)
		if (!strcmp([modList nameAt: i], name))
			view = [modList viewAt: i];
	if (!view)
		view = [self loadView:name];
	return view;
}

- objectNamed:(const char *)name
{
	int i, n;
	n = [modList count];
	for (i = 0; i < n; i++)
		if (!strcmp([modList nameAt: i], name))
			return [modList objectAt: i];
	return nil;
}

- (int)cellNamed:(const char *)name
{
	int i, n;

	n = [matrix cellCount];
	for (i = 0; i < n; i++)
		if (!strcmp([[matrix cellAt: i :0] stringValue], name))
			return i;
	return -1;
}

// this is pretty much a duplicate of - backView from ThinkMore.m
// I couldn't just call that directly because it relies on its own state
// to know what view to load
- loadView:(const char *)name
{
	NXRect aFrame = {{0,0},{200,200}};
	id theView = nil;
	char path[MAXPATHLEN];
	id myClass;
	char *filenames[] = {path, NULL};
	ModuleInfo *mp;
	struct mach_header *header;

	//if (index(name,' '))
	//	name = strip_spaces(name);
	mp = [self objectNamed:name];
	if ([mp path]) {
		long ret;
		while (1)
		{
			sprintf(path, "%s/%sView.BackO", [mp path], name);
			ret = objc_loadModules(filenames, NULL, NULL, &header, NULL);
			if (ret && [mp respondsTo:@selector(useNextPath)])
				if ([mp useNextPath])
					continue;
			break;
		}
		if (ret)
		{
			NXRunAlertPanel([NXApp appName], NXLocalString("Could not dynamically load class: %sView",0,0),
				NULL, NULL, NULL, name);
			return nil;
		}
		else
		{
			[mp setHeader:header];
		}
		//at this point we must have a valid name for a loaded class
	}	
	sprintf(path,"%sView", name);
	myClass = objc_getClass(path);

	// heh... god I love @defs! :-)
	theView = [[myClass allocFromZone:pub->backZone] initFrame:&aFrame];
	[mp setView:theView];
	// fake 'em out by passing thinker instead of self
	if ([theView respondsTo:@selector(inspector:)])
		[theView inspector:thinker];

	return theView;
}

- drawSelf:(const NXRect *)frm :(int)count
{
	PSsetgray(0.0);
	NXRectFillList(frm,count);
	return self;
}

- (BOOL)useBufferedWindow
{
  return YES;
}

- (const char *)windowTitle
{
  return "MultiView";
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
	[super sizeTo:width :height];
	[self newWindow];
	return self;
}

//
// handoff methods... these all just call the equivalent in Thinker
// some of the modules depend on them
//

- commonImageInspector
{
	return [thinker commonImageInspector];
}

- nullInspector
{
	return [thinker nullInspector];
}

- spaceInspector
{
	return [thinker spaceInspector];
}

- boinkInspector
{
	return [thinker boinkInspector];
}

- (const char *)moduleDirectory:(const char *)name
{
	return [thinker moduleDirectory:name];
}


@end
